package com.sweet.file;

import com.sweet.io.file.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

/**
 * 文件上传
 *
 * @author limi
 * @version 1.2.1 2016-11-04 17:10
 */
@Slf4j
public final class FileUpload {
    /**
     * 定义文件上传类型<br/>
     * spring(spring封装)、stream(流)、util(工具)
     *
     * @author limi
     * @version 0.0.1 2016年11月4日下午5:04:05
     */
    public enum UploadType {
        SPRING, STREAM, UTIL
    }

    /**
     * 文件上传(默认基于spring)
     *
     * @return 返回上传后的文件相对路径列表
     */
    public static List<String> upload(String projectPath, String uploadDirectory, MultipartFile[] files, List<String> allowSuffixs) {
        return upload(UploadType.SPRING, projectPath, uploadDirectory, files, allowSuffixs, false, false);
    }

    /**
     * 文件上传(默认基于spring,性能最好)
     *
     * @return 返回上传后的文件路径列表
     */
    public static List<String> upload(String projectPath, String uploadDirectory, MultipartFile[] files, List<String> allowSuffixs, boolean returnAbsolutePath, boolean useOriginalName) {
        return upload(UploadType.SPRING, projectPath, uploadDirectory, files, allowSuffixs, returnAbsolutePath, useOriginalName);
    }

    /**
     * 文件上传
     *
     * @param type               上传方式,来自枚举类型UploadType
     * @param projectPath        项目地址，如：c:/apache-tomcat/webapps/game/,最后一个/可有可无
     * @param uploadDirectory    上传目录，如：/resources/upload/,最后一个/可有可无
     * @param files              要上传的文件列表
     * @param allowSuffixs       允许上传的文件后缀
     * @param returnAbsolutePath 返回上传后的路径是否为绝对路径，true绝对路径，false相对路径
     * @param useOriginalName    是否使用文件原名称，而非重命名
     * @return
     */
    public static List<String> upload(UploadType type, String projectPath, String uploadDirectory, MultipartFile[] files, List<String> allowSuffixs, boolean returnAbsolutePath, boolean useOriginalName) {
        if (files == null || files.length == 0) {
            return null;
        }

        // 上传文件列表
        List<String> saveFilePaths = new ArrayList<String>();

        long allStart = System.currentTimeMillis();

        int index = 0;
        for (MultipartFile file : files) {
            index++;

            // 获取当前文件
            if (file == null || file.isEmpty()) {
                continue;
            }

            log.debug("===========开始上传第[{}]个文件===========", index);

            // 实际返回值如：上传的表单域名=photo,文件类型=text/plain,size=1598,原来的文件名=eroor.txt
            log.debug("-->上传的表单域名={},文件类型={},size={}", new Object[]{file.getName(), file.getContentType(), file.getSize()});

            // 获取原文件名
            String originalName = file.getOriginalFilename();

            // 获取文件后缀,如.txt
            String suffix = FileUtil.getFileSuffix(originalName);

            // 后缀检测
            if (allowSuffixs != null && !allowSuffixs.contains(suffix.toLowerCase())) {
                log.warn("-->文件{}上传失败，后缀被限制！", originalName);
                continue;
            }

            // 定义新文件名
            String newName = useOriginalName ? originalName : defineFileName(0, index, suffix);

            // 定义服务器保存路径
            String savePath = defineDirectoryPath(projectPath) + defineDirectoryPath(uploadDirectory) + newName;

            // 定义保存文件，目录不存在则创建
            File saveFile = new File(savePath);
            if (!saveFile.exists()) {
                File parent = saveFile.getParentFile();
                if (parent != null) {
                    parent.mkdirs();
                }
            }

            log.debug("-->旧文件名:{},新文件名:{}", originalName, newName);

            try {
                long oneStart = System.currentTimeMillis();

                // 根据不同方式上传
                if (type == UploadType.STREAM) {
                    // 获取输入流
                    FileInputStream in = (FileInputStream) file.getInputStream();

                    // 获取输出流
                    FileOutputStream os = new FileOutputStream(saveFile);

                    // 以写字节的方式写文件
                    int b = 0;
                    while ((b = in.read()) != -1) {
                        os.write(b);
                    }

                    // byte[] d = new byte[2048];
                    // while ((b = is.read(d)) !=-1) {
                    // os.write(d, 0, b);
                    // }

                    os.flush();
                    os.close();
                    in.close();
                } else if (type == UploadType.UTIL) {
                    FileUtils.copyInputStreamToFile(file.getInputStream(), saveFile); // 使用流拷贝
                } else if (type == UploadType.SPRING) {
                    file.transferTo(saveFile); // (推荐)使用转存,自动会选择使用stream还是util
                }

                log.info("第[{}]个文件上传完毕,耗时[{}]毫秒......", new Object[]{index, System.currentTimeMillis() - oneStart});
            } catch (Exception e) {
                e.printStackTrace();
                log.error("文件上传异常！", e);
            }

            // 保存该文件路径
            if (returnAbsolutePath) {
                saveFilePaths.add(savePath);
            } else {
                saveFilePaths.add(uploadDirectory + newName);
            }
        }

        log.info("所有文件上传完毕,总耗时[{}]毫秒......", System.currentTimeMillis() - allStart);

        // 返回成功文件列表
        return saveFilePaths;
    }

    /**
     * 定义新名称
     *
     * @param uid    当前用户id
     * @param index  文件序号
     * @param suffix 后缀
     * @return
     */
    private static String defineFileName(int uid, int index, String suffix) {
        StringBuilder name = new StringBuilder(32);
        name.append(new SimpleDateFormat("yyyyMMhhHHmmssSSS").format(new Date()));
        name.append("_").append(uid);
        name.append("_").append(index);
        name.append(suffix);

        return name.toString();
    }

    /**
     * 判断并将目录定义完整
     *
     * @param path
     * @return
     */
    private static String defineDirectoryPath(String path) {
        if (path.endsWith("/") || path.endsWith("\\")) {
            return path;
        }

        return path + "/";
    }

    /**
     * (有BUG)自动检测Request是否包含文件并进行上传<br/>
     * bug：始终只检测到一个文件
     *
     * @param request
     * @param projectPath
     * @param uploadDirectory
     * @return
     */
    @SuppressWarnings("unused")
    @Deprecated
    private List<String> uploadBySpring(HttpServletRequest request, String projectPath, String uploadDirectory) {
        // 上传文件列表
        List<String> saveFilePaths = new ArrayList<String>();

        int index = 0;

        // 创建一个通用的多部分解析器
        MultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());

        // 判断是否有文件需要上传
        if (multipartResolver.isMultipart(request)) {
            // 转换成多部分request
            MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;

            // 取得request中的所有文件名
            Iterator<String> iter = multiRequest.getFileNames();

            while (iter.hasNext()) {
                index++;

                // 取得上传文件
                MultipartFile file = multiRequest.getFile(iter.next());
                if (file == null || file.isEmpty()) {
                    continue;
                }

                log.debug("===========开始上传第[{}]个文件===========", index);

                // 实际返回值如：上传的表单域名=photo,文件类型=text/plain,size=1598,原来的文件名=eroor.txt
                log.debug("-->上传的表单域名={},文件类型={},size={}", new Object[]{file.getName(), file.getContentType(), file.getSize()});

                // 获取文件名
                String originalName = file.getOriginalFilename();
                String newName = defineFileName(0, index, originalName);

                // 定义服务器保存路径
                String savePath = projectPath + uploadDirectory + newName;

                // 定义保存文件
                File saveFile = new File(savePath);

                log.debug("-->旧文件名:{},新文件名:{}", originalName, newName);

                try {
                    long start = System.currentTimeMillis();

                    // 转存文件
                    file.transferTo(saveFile);

                    log.debug("第[{}]个文件上传完毕,耗时[{}]毫秒......", index, System.currentTimeMillis() - start);
                } catch (IllegalStateException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                // 保存该文件
                saveFilePaths.add(uploadDirectory + newName);
            }
        }

        // 返回成功文件列表
        return saveFilePaths;
    }
}